package com.sojson.util.security.springsecurity.service.impl;

import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.sojson.config.exception.TransErrorCode;
import com.sojson.config.exception.TransException;
import com.sojson.constant.Constant;
import com.sojson.entity.po.Member;
import com.sojson.service.AuthcService;
import com.sojson.service.MemberService;
import com.sojson.service.RoleService;
import com.sojson.util.DateUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.enums.ELoginOtherType;
import com.sojson.util.pay.wx.WxUtil;
import com.sojson.util.pay.wx.bean.WxResult;
import com.sojson.util.pwd.PwdUtil;
import com.sojson.util.security.springsecurity.service.CustomUserDetailsService;
import com.sojson.util.security.springsecurity.service.PermissionService;
import com.sojson.util.servlet.ServletUtil;
import com.sojson.util.token.Token;
import com.sojson.util.token.TokenUtil;
import com.sojson.util.token.TokenVo;

/**
 * 用户验证处理
 *
 * @author lzlg
 */
@Service
public class UserDetailsServiceImpl implements CustomUserDetailsService {

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private MemberService memberService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private AuthcService authcService;
    /** 前端传过来的参数在请求域中的Key */
    public static final String AUTHENTICATION_TOKEN = "AUTHENTICATION_TOKEN";

    /**
     * 登录<BR/>
     * 1.获取帐号密码<BR/>
     * 2.保存参数<BR/>
     * 3.调用登录接口(获取用户信息)<BR/>
     * 4.初始化用户信息<BR/>
     * 5.刷新Token<BR/>
     * 
     * @param param
     * @return
     * @throws Exception 
     */
    @Override
    public Token submitLogin(TokenVo param) throws Exception {
        // 用户信息
        Member member = param.getMember();
        // 帐号
        String username = null;
        if (ELoginOtherType.GENERAL.getCode() == param.getLoginType()) {
            username = member.getUsername();
        } else if (ELoginOtherType.MOBILE.getCode() == param.getLoginType()) {
            username = member.getMobile();
            member.setPwd(Constant.USER_PASSWORD_DEFAULT);
        } else if (ELoginOtherType.WX.getCode() == param.getLoginType()) {
            WxResult xcxLogin = WxUtil.xcxLogin(param.getUsername());
            member.setOpenid(xcxLogin.getOpenid());
            username = xcxLogin.getOpenid();
            member.setPwd(Constant.USER_PASSWORD_DEFAULT);
        } else {
            throw new TransException(TransErrorCode.NOT_EXIST_USER_TYPE);
        }

        // 保存参数
        ServletUtil.setValRequest(UserDetailsServiceImpl.AUTHENTICATION_TOKEN, param);

        // 用户验证
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        Authentication authenticate =
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, member.getPwd()));
        Token token = (Token)authenticate.getPrincipal();

        // 刷新Token
        userDetailsServiceImpl.submitLoginUpdateToken(token);

        return token;
    }

    /**
     * 获取用户信息(类似ShiroRealm)<BR/>
     * 调用AuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));的时候触发
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 获取参数
        TokenVo param = (TokenVo)ServletUtil.getValRequest(AUTHENTICATION_TOKEN);
        Set<String> roles = param.getRoles();

        // 获取用户
        Member member = null;
        try {
            if (ELoginOtherType.GENERAL.getCode() == param.getLoginType()) {
                member = memberService.findByRoleUsername(roles, username);
                if (StringUtil.isBlankObject(member)) {
                    throw new TransException(TransErrorCode.NOT_USER);
                }
                // 判断密码是否正确
                if (!PwdUtil.matchesPwd(param.getPassword(), member.getPwd())) {
                    throw new TransException(TransErrorCode.ERR_PWD);
                }
            } else if (ELoginOtherType.MOBILE.getCode() == param.getLoginType()) {
                member = memberService.findByRoleMobile(roles, username);
                // 用户不存在就注册
                if (StringUtil.isBlankObject(member)) {
                    member = authcService.smsRegister(param);
                }
                member.setPwd(Constant.USER_PASSWORD_DEFAULT_ENCODE);
            } else if (ELoginOtherType.WX.getCode() == param.getLoginType()) {
                member = memberService.findByRoleOpenid(roles, username);
                // 用户不存在就注册
                if (StringUtil.isBlankObject(member)) {
                    member = authcService.xcxRegister(param);
                }
                member.setPwd(Constant.USER_PASSWORD_DEFAULT_ENCODE);
                Member pam = param.getMember();
                String headImgParam = pam.getHeadImg();
                String nickNameParam = pam.getNickName();
                String headImg = member.getHeadImg();
                String nickName = pam.getNickName();
                if (StringUtil.isBlankObject(headImg) || !headImg.equals(headImgParam)
                    || StringUtil.isBlankObject(nickName) || !nickName.equals(nickNameParam)) {
                    Member update = new Member();
                    update.setId(member.getId());
                    update.setHeadImg(headImgParam);
                    update.setNickName(nickNameParam);
                    memberService.updatePartialById(update);
                }
            }
        } catch (Exception e) {
            throw new TransException(e.getMessage());
        }

        // 判断登录的用户是否存在
        if (StringUtil.isBlankObject(member)) {
            throw new TransException(TransErrorCode.NOT_USER);
        }
        // 判断登录的用户是否被禁用
        boolean submitLoginIsDisable = TokenUtil.submitLoginIsDisable(member);
        if (submitLoginIsDisable) {
            throw new TransException(TransErrorCode.USER_FORBID_LOGIN);
        }

        // 设置统一的帐号
        member.setUsername(username);

        Token createToken = null;
        try {
            // 创建并初始化Token
            createToken = userDetailsServiceImpl.createToken(member, param);
        } catch (Exception e) {
            throw new TransException(e.getMessage());
        }
        return createToken;
    }

    /**
     * 创建并初始化Token
     * 
     * @param member
     * @param param
     * @return
     * @throws Exception 
     */
    public Token createToken(Member member, Token param) throws Exception {
        Token token = null;
        // 删除旧Token
        Token lowToken = TokenUtil.getToken();
        if (StringUtil.isNotBlankObject(lowToken)) {
            TokenUtil.removeToken(lowToken.getId());
        }

        // 创建新Token
        token = TokenUtil.createToken();
        token.setMember(member);
        token.setLoginTime(DateUtil.nowLong());
        token.setRoles(roleService.getRolePermissionByUserId(member.getId()));
        token.setPermissions(permissionService.getMenuPermission(member, token.getRoles()));

        // 给用户赋初值
        userDetailsServiceImpl.userLoginInitGeneral(token, param);
        return token;
    }

    /**
     * 用户登录后给用户赋初值(普通登录方式)
     * 
     * @param token 要赋值的对象
     * @param param 客户端传过来的参数
     * @throws Exception
     */
    public void userLoginInitGeneral(Token token, Token param) throws Exception {
        // 设置登录类型
        token.setLoginType(param.getLoginType());
        // 设置登录设备
        token.setTypeLoginDevice(param.getTypeLoginDevice());
        // 设置保存在线用户的key
        token.setKeyLoginDevice();

        userDetailsServiceImpl.userLoginInit(token.getMember(), param);
    }

    /**
     * 用户登录后给用户赋初值
     * 
     * @param user  要赋值的对象
     * @param param 客户端传过来的参数
     * @throws Exception
     */
    public void userLoginInit(Member user, Token param) throws Exception {
        // 设置保存在线用户的key
        // user.setCellphoneOrComputerKey();
    }

    /**
     * 登录后更新Token
     * 
     * @param token
     * @throws Exception
     */
    public void submitLoginUpdateToken(Token token) throws Exception {
        /** 将用户保存到在线用户中 */
        TokenUtil.setUserToOnlineUserCache();

        // 更新Token
        TokenUtil.refreshToken(token);
    }

}